package com.nulldev.util.web.YouTubeExtractor.signatures.extractors;

import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import javax.script.ScriptEngine;
import javax.script.ScriptException;

import com.nulldev.util.JVM.JVM;
import com.nulldev.util.concurrency.threadIt.v4.emapi.ExecutorManager;
import com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Executable;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.scripting.ScriptAPI.nScripting;
import com.nulldev.util.scripting.ScriptAPI.plusExt.rhino.RhinoJSE;
import com.nulldev.util.scripting.ScriptAPI.plusExt.rhino.RhinoJSEFactory;
import com.nulldev.util.web.YouTubeExtractor.signatures.ExtractedSignatureData;
import com.nulldev.util.web.YouTubeExtractor.signatures.extractor.Extractor;

public class RhinoBasedExtractor extends Extractor implements Closeable {
	private volatile ScriptEngine ns;

	private final ExecutorManager __TEMPORARY_MT_FIX = new ExecutorManager(Executors.newSingleThreadExecutor(new ThreadFactory() {

		@Override
		public Thread newThread(Runnable r) {
			return new Thread(r, "GraalBasedExtractor: Execution Thread");
		}
	}));

	public RhinoBasedExtractor(final ExtractedSignatureData data) {
		super(data);
		if (!nScripting.isRhinoInstalled()) {
			throw new RuntimeException("Extractors.Rhino --> Rhino isn't installed.");
		}
		this.__TEMPORARY_MT_FIX.queueTask(new Runnable() {

			@Override
			public void run() {
				RhinoBasedExtractor.this.ns = new RhinoJSE(new RhinoJSEFactory());
				if (RhinoBasedExtractor.this.ns != null) {
					try {
						RhinoBasedExtractor.this.ns.eval(RhinoBasedExtractor.this.getCode());
					} catch (ScriptException e) {
						JVM.errorln(e);
					}
				}
			}
		});
	}

	@Override
	public String decrypt(final String signature) {
		if (signature == null)
			return null;
		try {
			final Executable<String> s = ExecutorManager.make(() -> {
				try {
					return (String) RhinoBasedExtractor.this.ns.eval(RhinoBasedExtractor.this.getFunctionName() + "(\"" + signature + "\");");
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
			});
			this.__TEMPORARY_MT_FIX.queueTask(s);
			return s.waitTillCompletion();
		} catch (RuntimeException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public Executable<String> decryptTask(final String signature) {
		return ExecutorManager.make(() -> {
			return decrypt(signature);
		});
	}

	@Override
	public String getCode() {
		return this.getExtractedData().getCode();
	}

	@Override
	public String getFunctionCode() {
		return this.getExtractedData().getFunctionCode();
	}

	@Override
	public String getFunctionName() {
		return this.getExtractedData().getFunctionName();
	}

	@Override
	public Extractor updateESD(final ExtractedSignatureData newData) {
		super.setExtractedData(newData);
		this.__TEMPORARY_MT_FIX.queueTask(new Runnable() {

			@Override
			public void run() {
				if (RhinoBasedExtractor.this.ns != null) {
					try {
						RhinoBasedExtractor.this.ns.eval("delete " + RhinoBasedExtractor.this.getFunctionName());
						RhinoBasedExtractor.this.ns.eval(RhinoBasedExtractor.this.getCode());
					} catch (ScriptException e) {
						JVM.errorln(e);
					}
				}
			}
		});
		return this;
	}

	@Override
	public void close() throws IOException {
		IOUtils.closeQuietly(this.__TEMPORARY_MT_FIX);
	}
}
